Skip to content

Order a category listing by the position set in the browsed category - #1289

Open
boo-code wants to merge 1 commit into
PrestaShop:devfrom
boo-code:fix/position-sort-browsed-category
Open

Order a category listing by the position set in the browsed category#1289
boo-code wants to merge 1 commit into
PrestaShop:devfrom
boo-code:fix/position-sort-browsed-category

Conversation

@boo-code

@boo-code boo-code commented Aug 9, 2026

Copy link
Copy Markdown
Contributor
Questions Answers
Description? Sorting a category listing by position orders products by their position in an arbitrary category of the subtree instead of the browsed one. A product has one position per category it belongs to; with PS_LAYERED_FULL_TREE the rows are restricted with nleft/nright rather than to the browsed category, so cp.position is not functionally dependent on the product the query groups by and the server keeps whichever row it likes. Leaf categories are unaffected, which is why a shop looks correct everywhere except parent categories. The ordering is now pinned to the browsed category, and a product reached only through a subcategory - which has no position there - is listed after the ones the merchant actually arranged.
Type? bug fix
BC breaks? no
Deprecations? no
Fixed ticket? Fixes PrestaShop/PrestaShop#42279, Fixes PrestaShop/PrestaShop#26564.
How to test? composer test. The added case asserts the generated SQL: the ordering expression is evaluated in the outer query, where category_product is joined, and is kept out of the initial population, which does not join it. Reverting only src/Adapter/MySQL.php makes the initial population select ISNULL(MIN(IF(cp.id_category = 6, cp.position, NULL))) against an alias it never joins, which is an Unknown column 'cp.id_category' at runtime. Manually: give products a deliberate order in a category that has subcategories and whose products also sit in those subcategories, turn on "Show products from subcategories", and browse it sorted by position.
Sponsor company

Why it is a real defect rather than a data quirk

The query is not valid aggregation - it only runs because PrestaShop sets sql_mode = '' in DbPDO.
Asking the same server to apply the standard rejects it outright:

ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains
nonaggregated column 'prestashop.cp.position' which is not functionally dependent on columns
in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

So which position wins is undefined, not merely unexpected. On the default data set of a 9.1 shop the
server happens to keep the browsed category's row and the listing looks right; forcing the other
candidate rows on the same data gives 16,6,17,7,18,8,… where the merchant's order is
7,6,8,10,9,11,…. That is the same symptom #42279 reports from a production shop, so this fixes a
latent defect whose visibility depends on the plan and the data rather than one that reproduces
everywhere.

The two changes

src/Filters/Products.php pins the ordering:

$positionInCategory = 'MIN(IF(cp.id_category = ' . (int) $query->getIdCategory() . ', cp.position, NULL))';
$orderBy = 'ISNULL(' . $positionInCategory . ') ASC, ' . $positionInCategory;

ISNULL(...) ASC keeps the products that have no position in the browsed category last in both
directions, which a sentinel value would not do on DESC.

src/Adapter/MySQL.php stops pushing an expression order field into the initial population.
computeOrderByField() already treats a value containing ( as an outer-query expression and returns
it untouched, but it had added it to the initial population's select list first. The initial population
does not carry the outer query's joins, so the expression there refers to tables that are not joined at
that level. Any caller passing an expression hits this, not just this one.

@github-project-automation github-project-automation Bot moved this to Ready for review in PR Dashboard Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

2 participants